﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using WebCalendar.Filters;
using WebCalendar.Models;
using WebCalendar.Models.Authorization;
using WebCalendar.Models.DTOs;
using WebMatrix.WebData;

namespace WebCalendar.Controllers
{
    //[InitializeSimpleMembership]
    [Authorize]
    public class ManageController : Controller
    {
        private WebCalendarContext db = new WebCalendarContext();

        //
        // GET: /Manage/

        public ActionResult Index()
        {
            var user = WebCalendarUser.GetUserProfile(db);
            var ownCalendars = user.CalendarRoles.OfType<OwnerRole>();
            var editCalendars = user.CalendarRoles.OfType<EditorRole>();
            var memberCalendars = user.CalendarRoles.OfType<MemberRole>();
            var categories = user.Categories;

            Category defaultCategory = db.Categories.Include("User").ToList().SingleOrDefault(c => c.User == user && c.Name == "unassigned");

            categories.Remove(defaultCategory);

            var co = ownCalendars.Select(m => m.Calendar).ToList();
            var ce = editCalendars.Select(m => m.Calendar).ToList();
            var cm = memberCalendars.Select(m => m.Calendar).ToList();
            var cc = categories.ToList();
            var model = new ManageIndexDTO { OwnCalendars = co, EditableCalendars = ce, MemberCalendars = cm, MyCategories = cc };

            return View(model);
        }

        //
        // GET: /Manage/Details/5

        public ActionResult Details(int id = 0)
        {
            Calendar calendar = db.Calendars.Find(id);
            if (calendar == null)
            {
                return HttpNotFound();
            }
            return View(calendar);
        }

        //
        // GET: /Manage/Create
        [Authorize]
        public ActionResult Create()
        {
            Calendar calendar = new Calendar { Name = "MyCalendar" };
            ManageCalendarCreateDTO mccDTO = new ManageCalendarCreateDTO { Calendar = calendar };
            return View(mccDTO);
        }

        //
        // POST: /Manage/Create
        [Authorize]
        [HttpPost]
        public ActionResult Create(ManageCalendarCreateDTO mccDTO)
        {
            if (ModelState.IsValid)
            {
                Calendar calendar = mccDTO.Calendar;
                var user = WebCalendarUser.GetUserProfile(db);
                db.Calendars.Add(calendar);
                user.CalendarRoles.Add(new OwnerRole { Calendar = calendar, User = user });

                if (!String.IsNullOrEmpty(mccDTO.NewCategoryName))
                {
                    db.Categories.Add(new Category { Name = mccDTO.NewCategoryName, User = user, Color = "#" + mccDTO.NewCategoryColor });
                }

                db.SaveChanges();
                return RedirectToAction("Index");
            }

            return RedirectToAction("Index");
        }

        //
        // GET: /Manage/Edit/5
        [Authorize]
        public ActionResult Edit(int id = 0)
        {
            var user = WebCalendarUser.GetUserProfile(db);
            var role = db.CalendarRoles.FirstOrDefault(r => r.Calendar.ID == id && r.User.UserId == user.UserId);
            if (role == null || !role.EditCalendar())
            {
                return HttpNotFound();
            }
            Calendar calendar = db.Calendars.Find(id);
            if (calendar == null)
            {
                return HttpNotFound();
            }

            Category dummy1 = new Category { ID = 0, Name = "none" };
            Category dummy2 = new Category { ID = 0, Name = "none" };

            List<Category> categoriesIn = new List<Category>();
            categoriesIn.Add(dummy1);
            categoriesIn = categoriesIn.Concat((from cat in db.Categories
                                                join e in db.PersonalEntries
                                                on cat.ID equals e.Category.ID
                                                join f in db.EntryFilters
                                                on e.ID equals f.Entry.ID
                                                join cal in db.Calendars
                                                on f.Calendar.ID equals cal.ID
                                                where cal.ID == calendar.ID
                                                && cat.User.UserId == user.UserId
                                                select cat).Distinct().ToList()).ToList();

            Category defaultCategory = db.Categories.Include("User").ToList().SingleOrDefault(c => c.User == user && c.Name == "unassigned");

            categoriesIn.Remove(defaultCategory);

            List<Category> categoriesOut = new List<Category>();

            categoriesOut.Add(dummy2);

            categoriesOut = categoriesOut.Concat((from cat in db.Categories
                                                  where cat.User.UserId == user.UserId
                                                  select cat).ToList()).ToList();

            categoriesOut.RemoveAll(c => categoriesIn.Contains(c));
            
            ManageCalendarEditDTO mceDTO = new ManageCalendarEditDTO { Calendar = calendar, AddableCategories = categoriesOut, RemovableCategories = categoriesIn, SelectedAddableCategory = 0, SelectedRemovableCategory = 0 };

            return View(mceDTO);
        }

        //
        // POST: /Manage/Edit/5
        [Authorize]
        [HttpPost]
        public ActionResult Edit(ManageCalendarEditDTO mceDTO)
        {
            Calendar calendar = db.Calendars.Include("EntryFilters").SingleOrDefault(c => c.ID == mceDTO.Calendar.ID);
            if (calendar == null)
            {
                return HttpNotFound();
            }
            if (ModelState.IsValid)
            {
                if (mceDTO.SelectedAddableCategory != 0)
                {

                    Category categoryToAdd = db.Categories.Include("User").Include("Entries").FirstOrDefault(c => c.ID == mceDTO.SelectedAddableCategory);

                    var categories = (from cx in db.Categories
                                      join ex in db.PersonalEntries
                                      on cx.ID equals ex.Category.ID
                                      join gx in db.EntryFilters
                                      on ex.ID equals gx.Entry.ID
                                      join calx in db.Calendars
                                      on gx.Calendar.ID equals calx.ID
                                      where calx.ID == calendar.ID
                                      select cx).Distinct();

                    var roles = db.CalendarRoles.Include("User").Include("Calendar").ToList().Where(r => r.Calendar == calendar);

                    UserProfile owner = roles.OfType<OwnerRole>().SingleOrDefault().User;
                    UserProfile user = WebCalendarUser.GetUserProfile(db);
                    Role actingRole = roles.SingleOrDefault(r => r.User.UserId == user.UserId);
                    if (categories.Any(cx => cx.ID == categoryToAdd.ID)) return RedirectToAction("Edit");

                    foreach (var e in categoryToAdd.Entries)
                    {
                        Entry entryBase = e.EntryBase;
                        EntryFilter newEF = new EntryFilter();
                        PersonalEntry pe = new PersonalEntry();
                        foreach (var r in roles)
                        {
                            pe = db.PersonalEntries.FirstOrDefault(p => p.EntryBase == entryBase && p.Category.User.UserId == r.User.UserId);
                            if (r != actingRole)
                            {
                                if (pe == null)
                                {
                                    Category defaultCategory = db.Categories.FirstOrDefault(c => c.User.UserId == r.User.UserId && c.Name == "unassigned");
                                    if (defaultCategory == null)
                                    {
                                        defaultCategory = new Category { User = r.User, Name = "unassigned", Color = "#ffffff", Entries = new List<PersonalEntry>() };
                                        db.Categories.Add(defaultCategory);
                                    }
                                    pe = new PersonalEntry { EntryBase = entryBase, Category = defaultCategory };
                                    db.PersonalEntries.Add(pe);
                                }
                                newEF = new EntryFilter { Entry = pe, Visible = true };
                                calendar.EntryFilters.Add(newEF);
                                db.EntryFilters.Add(newEF);
                            }
                        }
                        if (!calendar.EntryFilters.Any(f => f.Entry == e))
                        {
                            newEF = new EntryFilter { Entry = e, Visible = true };
                            calendar.EntryFilters.Add(newEF);
                            db.EntryFilters.Add(newEF);
                        }
                    }
                    db.SaveChanges();
                }
                if (mceDTO.SelectedRemovableCategory != 0)
                {
                    // calendar.RemoveCategory(categoryToRemove);
                    var filters = (from f in db.EntryFilters
                                   join p in db.PersonalEntries
                                   on f.Entry.ID equals p.ID
                                   join t in db.Categories
                                   on p.Category.ID equals t.ID
                                   join e in db.Entries
                                   on p.EntryBase.ID equals e.ID
                                   join c in db.Calendars
                                   on f.Calendar.ID equals c.ID
                                   where c.ID == calendar.ID
                                   && t.ID == mceDTO.SelectedRemovableCategory
                                   select new { f, e.ID }).Distinct();

                    foreach (var filter in filters)
                    {
                        db.EntryFilters.Remove(filter.f);
                    }
                }
                calendar.Name = mceDTO.Calendar.Name;
                db.Entry(calendar).State = EntityState.Modified;
                db.SaveChanges();
                return RedirectToAction("Edit");
            }
            return View(calendar);
        }

        //
        // GET: /Manage/Delete/5
        [Authorize]
        public ActionResult Delete(int id = 0)
        {
            Calendar calendar = db.Calendars.Find(id);
            if (calendar == null)
            {
                return HttpNotFound();
            }

            return View(calendar);
        }

        //
        // POST: /Manage/Delete/5
        [Authorize]
        [HttpPost, ActionName("Delete")]
        public ActionResult DeleteConfirmed(int id)
        {
            Calendar calendar = db.Calendars.Find(id);
            var roles = db.CalendarRoles.Where(r => r.Calendar.ID == id);
            foreach (var role in roles)
            {
                db.CalendarRoles.Remove(role);
            }
            var filters = db.EntryFilters.Where(f => f.Calendar.ID == id);
            foreach (var filter in filters)
            {
                db.EntryFilters.Remove(filter);
            }
            db.Calendars.Remove(calendar);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        //
        // GET: /Manage/CreateCategory/5
        [Authorize]
        public ActionResult CreateCategory()
        {
            return View();
        }

        //
        // POST: /Manage/CreateCategory/5
        [Authorize]
        [HttpPost]
        public ActionResult CreateCategory(Category category)
        {
            if (ModelState.IsValid)
            {
                var user = WebCalendarUser.GetUserProfile(db);
                
                if (!category.Color.StartsWith("#"))
                {
                    category.Color = "#" + category.Color;
                }
                category.User = user;
                db.Categories.Add(category);
                user.Categories.Add(category);
                db.SaveChanges();
                return RedirectToAction("Index");
            }

            return View(category);
        }

        //
        // GET: /Manage/CalendarRoles/5
        [Authorize]
        public ActionResult CalendarRoles(int id)
        {
            var actingUser = WebCalendarUser.GetUserProfile(db);
            var calendar = db.Calendars.Find(id);
            var roles = db.CalendarRoles.Include("User").ToList().Where(r => r.Calendar == calendar);
            var actingRole = roles.FirstOrDefault(r => r.User == actingUser);

            if (!actingRole.ManageRoles()) return HttpNotFound();

            var users = new List<ManageUserRoleDTO>();
            foreach (var role in roles)
            {
                if (role != actingRole) users.Add(new ManageUserRoleDTO { User = role.User, RoleName = role.RoleName(), RoleId = role.ID });
            }
            users = users.Distinct().OrderBy(u => u.User.UserName).ToList();

            ManageCalendarRolesDTO mcrDTO = new ManageCalendarRolesDTO { Calendar = calendar, Users = users, AvailableRoles = new List<string>{ "Editor", "Member"} , NewUser = "", NewRole = "" };

            return View(mcrDTO);
        }

        //
        // POST: /Manage/CalendarRoles/5
        [Authorize]
        [HttpPost]
        public ActionResult CalendarRoles(ManageCalendarRolesDTO mcrDTO)
        {
            var actingUser = WebCalendarUser.GetUserProfile(db);
            var roles = db.CalendarRoles.Include("User").Include("Calendar").ToList().Where(r => r.Calendar.ID == mcrDTO.Calendar.ID);
            var actingRole = roles.FirstOrDefault(r => r.User == actingUser);
            var users = new List<UserProfile>();

            if (!actingRole.ManageRoles()) return HttpNotFound();

            foreach(var r in roles)
            {
                users.Add(r.User);
            }

            users.Remove(actingUser);

            if (mcrDTO.NewUser != null)
            {
                UserProfile newUser = db.UserProfiles.ToList().Where(u => u.UserName == mcrDTO.NewUser).SingleOrDefault();
                Calendar calendar = db.Calendars.Find(mcrDTO.Calendar.ID);

                if (newUser == null) return RedirectToAction("CalendarRoles");
                               
                if (users.Count() == 0 || !users.Any(u => u.UserName == mcrDTO.NewUser))
                {
                    Role newRole = new MemberRole();
                    if (mcrDTO.NewRole == "Editor")
                    {
                        newRole = new EditorRole { User = newUser, Calendar = calendar };
                    }
                    else if (mcrDTO.NewRole == "Member")
                    {
                        newRole = new MemberRole { User = newUser, Calendar = calendar };
                    }
                    var entries = (from e in db.Entries
                                    join p in db.PersonalEntries
                                    on e.ID equals p.EntryBase.ID
                                    join f in db.EntryFilters
                                    on p.ID equals f.Entry.ID
                                    join c in db.Calendars
                                    on f.Calendar.ID equals c.ID
                                    where c.ID == calendar.ID
                                    select e).Distinct().ToList();

                    foreach (var e in entries)
                    {
                        var pentries = (from p in db.PersonalEntries
                                        join n in db.Entries
                                        on p.EntryBase.ID equals n.ID
                                        join c in db.Categories
                                        on p.Category.ID equals c.ID
                                        join u in db.UserProfiles
                                        on c.User.UserId equals u.UserId
                                        where u.UserId == newUser.UserId
                                        && n.ID == e.ID
                                        select p).Distinct().ToList();

                        var pe = new PersonalEntry();

                        if (pentries.Count() == 0)
                        {
                            Category defaultCategory = db.Categories.Include("User").ToList().Where(c => c.User.UserId == newUser.UserId && c.Name == "unassigned").FirstOrDefault();
                            pe = new PersonalEntry { Category = defaultCategory, EntryBase = e };
                            db.PersonalEntries.Add(pe);
                        }
                        else
                        {
                            pe = pentries.First();
                        }
                        EntryFilter newEF = new EntryFilter { Calendar = calendar, Entry = pe };
                        calendar.EntryFilters.Add(newEF);
                        db.EntryFilters.Add(newEF);
                        db.CalendarRoles.Add(newRole);
                    }
                }
            }

            db.SaveChanges();
            return RedirectToAction("CalendarRoles", mcrDTO.Calendar.ID);
        }

        [Authorize]
        [HttpGet]
        public ActionResult Promote(int id, int roleId)
        {
            var actingUser = WebCalendarUser.GetUserProfile(db);
            var calendar = db.Calendars.Find(id);
            var roles = db.CalendarRoles.Include("User").ToList().Where(r => r.Calendar == calendar);
            var actingRole = roles.FirstOrDefault(r => r.User == actingUser);

            if (!actingRole.ManageRoles()) return HttpNotFound();

            var role = db.CalendarRoles.Find(roleId);
            var user = role.User;

            db.CalendarRoles.Remove(role);
            db.CalendarRoles.Add(new EditorRole { Calendar = calendar, User = user });

            db.SaveChanges();

            return RedirectToAction("CalendarRoles", new { id = id });
        }

        [Authorize]
        [HttpGet]
        public ActionResult Demote(int id, int roleId)
        {
            var actingUser = WebCalendarUser.GetUserProfile(db);
            var calendar = db.Calendars.Find(id);
            var roles = db.CalendarRoles.Include("User").ToList().Where(r => r.Calendar == calendar);
            var actingRole = roles.FirstOrDefault(r => r.User == actingUser);

            if (!actingRole.ManageRoles()) return HttpNotFound();

            var role = db.CalendarRoles.Find(roleId);
            var user = role.User;

            var filters = (from f in db.EntryFilters
                           join p in db.PersonalEntries
                           on f.Entry.ID equals p.ID
                           join c in db.Categories
                           on p.Category.ID equals c.ID
                           join u in db.UserProfiles
                           on c.User.UserId equals u.UserId
                           join cal in db.Calendars
                           on f.Calendar.ID equals cal.ID
                           where cal.ID == id
                           && u.UserId == role.User.UserId
                           select f).ToList();

            foreach (var f in filters)
            {
                db.EntryFilters.Remove(f);
            }

            db.CalendarRoles.Remove(role);
            db.CalendarRoles.Add(new MemberRole { Calendar = calendar, User = user });

            db.SaveChanges();

            return RedirectToAction("CalendarRoles", new { id = id });
        }

        [Authorize]
        [HttpGet]
        public ActionResult RemoveRole(int id, int roleId)
        {
            var actingUser = WebCalendarUser.GetUserProfile(db);
            var calendar = db.Calendars.Find(id);
            var roles = db.CalendarRoles.Include("User").ToList().Where(r => r.Calendar == calendar);
            var actingRole = roles.FirstOrDefault(r => r.User == actingUser);

            if (!actingRole.ManageRoles()) return HttpNotFound();

            var role = db.CalendarRoles.Find(roleId);
            var user = role.User;

            db.CalendarRoles.Remove(role);

            db.SaveChanges();

            return RedirectToAction("CalendarRoles", new { id = id });
        }

        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
        
        //
        // GET: /Manage/EditCategory
        [Authorize]
        public ActionResult EditCategory(int id)
        {
            UserProfile user = WebCalendarUser.GetUserProfile(db);
            Category category = db.Categories.Find(id);
            if (category.User != user)
            {
                return HttpNotFound();
            }
            return View(category);
        }

        //
        // POST: /Manage/EditCategory
        [Authorize]
        [HttpPost]
        public ActionResult EditCategory(Category category)
        {
            Category editCategory = db.Categories.Find(category.ID);
            UserProfile user = WebCalendarUser.GetUserProfile(db);
            
            if (editCategory.User != user)
            {
                return HttpNotFound();
            }

            if (ModelState.IsValid)
            {
                editCategory.Name = category.Name;
                editCategory.Color = category.Color;
                db.Entry(editCategory).State = EntityState.Modified;
                db.SaveChanges();
            }

            return RedirectToAction("EditCategory",category.ID);
        }

        //
        // GET: /Manage/DeleteCategory/5
        [Authorize]
        public ActionResult DeleteCategory(int id = 0)
        {
            Category category = db.Categories.Find(id);
            
            if (category == null)
            {
                return HttpNotFound();
            }
            
            UserProfile user = WebCalendarUser.GetUserProfile(db);

            if (category.User != user)
            {
                return HttpNotFound();
            }

            return View(category);
        }

        //
        // POST: /Manage/Delete/5
        [Authorize]
        [HttpPost, ActionName("DeleteCategory")]
        public ActionResult DeleteCategoryConfirmed(int id)
        {
            UserProfile user = WebCalendarUser.GetUserProfile(db);
            Category category = db.Categories.Find(id);
            
            if (category.User != user)
            {
                return HttpNotFound();
            }
            
            
            Category defaultCategory = db.Categories.Include("User").ToList().SingleOrDefault(c => c.User == user && c.Name == "unassigned");

            var entries = (from p in db.PersonalEntries
                           join c in db.Categories
                           on p.Category.ID equals c.ID
                           where c.ID == category.ID
                           select p).ToList();

            foreach (var e in entries)
            {
                e.Category = defaultCategory;
                db.Entry(e).State = EntityState.Modified;
            }
            if (category == null)
            {
                return HttpNotFound();
            }

            db.Categories.Remove(category);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
    }
}